package Statistics;

import java.util.ArrayList;
import java.util.Collections;

import Definitions.GraphClass;
import Definitions.NodeClass;
import Utility.WorkerUtilityClass;

public class DegreeStatistics
{	
	public class DegreeHolderClass  implements Comparable<DegreeHolderClass> 
	{
	
		/*Holds the degree levels found in the linkage data*/
		Integer degreeLevel;
		/*Holds the degrees corresponding to the degree levels found in the linkage data*/
		int degreeFrequency;
		
		//These two are related to Degree-Degree Correlation.
		Double sumOfDegreesInTheNeighbourhoodOfThisDegreeLevel;
		int totalNumberOfNodesInTheNeghbourhoodOfThisDegreeLevel;

		Double richClubCoefficientOfDegreeLevel;
		
		public DegreeHolderClass(int nodesDegree)
		{
			degreeLevel=nodesDegree;
			degreeFrequency = 1;			
			richClubCoefficientOfDegreeLevel=0.0;			
			sumOfDegreesInTheNeighbourhoodOfThisDegreeLevel = 0.0;
			totalNumberOfNodesInTheNeghbourhoodOfThisDegreeLevel = 0;
		}
		
		public boolean addNodeDegreeToTheCurrentDegree(int nodesDegree)
		{
			if(degreeFrequency+1<Integer.MAX_VALUE)
			{
				degreeFrequency++;
				return true;							
			}
			else
				return false;			
		}
		
		public int getDegreeLevel()
		{
			return degreeLevel;			
		}
		
		public int getDegreeOfTheLevel()
		{
			return degreeFrequency;			
		}
		

		public int compareTo(DegreeHolderClass d)
		{
			return degreeLevel.compareTo(d.degreeLevel);
			
		}		
	}
		
	ArrayList <DegreeHolderClass> degreeLevelsArrayList;
	int totalDegree;
	
	GraphClass currentGraph;
	int edgeType;
	
	
	public DegreeStatistics(GraphClass g, int edgeType)
	{
		currentGraph = g;
		this.edgeType = edgeType;
		degreeLevelsArrayList = new ArrayList<DegreeHolderClass>();		
	}	
	
	public void calculateDegreeStatistics()	
	{
		
		int total=0;
		ArrayList <NodeClass> dataSet = currentGraph.getDataSet();
		int sizeOfTheDataSet = currentGraph.getDataSet().size();
		int indexOfTheDegreeLevel;

		double[] degreeListOfNodes = new double[sizeOfTheDataSet];
		int numberOfNeighboursOfTheNode;

		for(int i=0; i<sizeOfTheDataSet; i++)
		{
			numberOfNeighboursOfTheNode = dataSet.get(i).getNumberOfNeigbours(edgeType);
			degreeListOfNodes[i] = numberOfNeighboursOfTheNode;

			total += numberOfNeighboursOfTheNode;
			indexOfTheDegreeLevel = addToTheDegreeStatistics(numberOfNeighboursOfTheNode);
			addToTheDegreeDegreeCorrelationStatistics(indexOfTheDegreeLevel, dataSet.get(i).getTotalDegreeOfTheNeighbours(edgeType), numberOfNeighboursOfTheNode);

		}

		sort();
		findTheTotalDegree();

		double averageDegree;	  	
		averageDegree = (double)total/currentGraph.getDataSet().size();
		int i=0;		
		
		int numberOfTheRealDegreeLevels = degreeLevelsArrayList.size();
		
		//AS:We do not include disjoint nodes.
		if(degreeLevelsArrayList.get(0).getDegreeLevel()==0)
		{		
			numberOfTheRealDegreeLevels = degreeLevelsArrayList.size()-1;
			i=1;
		}

		double[] degreeLevels = new double[numberOfTheRealDegreeLevels];
		double[] degreeFrequencyForTheLevels = new double[numberOfTheRealDegreeLevels];

		DegreeHolderClass holderForCurrentDegreeLevel;

		for(int j=0; j<numberOfTheRealDegreeLevels; i++, j++)
		{
			holderForCurrentDegreeLevel = degreeLevelsArrayList.get(i);	  		
			degreeLevels[j]=holderForCurrentDegreeLevel.getDegreeLevel();
			//System.out.println("degreeFrequencyForTheLevels["+i+"]:"+holderForCurrentDegreeLevel.getDegreeOfTheLevel()/sizeOfTheDataSet);
			degreeFrequencyForTheLevels[j]=(double)holderForCurrentDegreeLevel.getDegreeOfTheLevel()/sizeOfTheDataSet;	  		
		}	  	

		currentGraph.getPropertiesOfTheGraphWithRespectToEdgeType().get(edgeType).degreeDistributionRelatedProps.setDegreeDistributionRelatedParameters(degreeListOfNodes, degreeLevels, degreeFrequencyForTheLevels, averageDegree);

		System.out.println("Total Degree:" + total+"\nAverage Degree:"+averageDegree);
	}
	
	void calculateRichClubCoefficientsWrtDegreeLevels()
	{
		double richClubCoefficientOfTheDegreeLevel=0.0;

		
		int numberOfTheRealDegreeLevels = degreeLevelsArrayList.size();
		int i=0;
		
		//AS:We do not include disjoint nodes.
		if(degreeLevelsArrayList.get(0).getDegreeLevel()==0)
		{		
			numberOfTheRealDegreeLevels = degreeLevelsArrayList.size()-1;
			i=1;
		}
		double[] richClubCoefficientArrayWrtDegreeLevels = new double[numberOfTheRealDegreeLevels];
		
		for(int j=0; i<numberOfTheRealDegreeLevels; i++, j++)
		{			
			richCLubCoefficientsClass richClubCoefficients = new richCLubCoefficientsClass(currentGraph, getDegreeLevelAtIndex(i), edgeType);
			//System.out.println("RichClubCoefficient["+degreeStatistics.getDegreeLevelAtIndex(i)+"]" + );
			richClubCoefficients.calculateRichClubCoefficientsForTheGivenThreshold();
			richClubCoefficientOfTheDegreeLevel = richClubCoefficients.calculateRichClubCoefficientOfTheGraph();
			richClubCoefficientArrayWrtDegreeLevels[j] = richClubCoefficientOfTheDegreeLevel;
			setRichClubCoefficientOfDegreeLevel(i, richClubCoefficientOfTheDegreeLevel);
			//richClubCoefficients.printRichClubCoefficientOfTheGraph(currentGraph);			
		}
		currentGraph.getPropertiesOfTheGraphWithRespectToEdgeType().get(edgeType).richClubCoefficientRelatedProps.setRichClubCoefficientVsDegreeDistribution(richClubCoefficientArrayWrtDegreeLevels);
		
	}
	
	public void addToTheDegreeDegreeCorrelationStatistics(int indexOfThisDegreeLevel, int sumOfNodesNeighboursDegrees, int numberOfNeighboursOfTheNode)
	{
		degreeLevelsArrayList.get(indexOfThisDegreeLevel).sumOfDegreesInTheNeighbourhoodOfThisDegreeLevel +=(double)sumOfNodesNeighboursDegrees;
		degreeLevelsArrayList.get(indexOfThisDegreeLevel).totalNumberOfNodesInTheNeghbourhoodOfThisDegreeLevel +=numberOfNeighboursOfTheNode;		
	}
	
	public int addToTheDegreeStatistics(int nodesDegree)	
	{
		int indexOfTheDegreeLevel;
		
		if((indexOfTheDegreeLevel=doesThisDegreeLevelAddedBeforeToTheList(nodesDegree))==-1)
		{
			degreeLevelsArrayList.add(new DegreeHolderClass(nodesDegree));
			indexOfTheDegreeLevel=degreeLevelsArrayList.size()-1;
		}
		else
			degreeLevelsArrayList.get(indexOfTheDegreeLevel).addNodeDegreeToTheCurrentDegree(nodesDegree);
		
		return indexOfTheDegreeLevel; 
	}
	
	int doesThisDegreeLevelAddedBeforeToTheList(int degreeLevel)
	{
		for(int i=0; i<degreeLevelsArrayList.size(); i++)
			if(degreeLevelsArrayList.get(i).getDegreeLevel()==degreeLevel)
				return i;
		
		return -1;		
	}
	
	public int findTheTotalDegree()
	{
		int total=0;
		for(int i=0; i<degreeLevelsArrayList.size(); i++)
			total += degreeLevelsArrayList.get(i).getDegreeLevel() * degreeLevelsArrayList.get(i).getDegreeOfTheLevel();
		
		System.out.println("Total degree:" + total);
		WorkerUtilityClass.printSeperator();
		totalDegree = total;
		return total;	
	}
	
	public void print()
	{
		printDegreeLevelsInMatlabFormat();
		printRichClubCoefficientsInMatlabFormat();
		printDegreeOfTheLevelsProbabilityInMatlabFormat();
		printMinimalList();
		//printWithZeros();
		//printDegreeDegreeCorrelation();
		printDegreeDegreeCorrelationInMatlabFormat();
	}
	
	void printDegreeDegreeCorrelation()
	{		
		System.out.println("Now printing the degree degree correlations:");
		for(int i=0; i< degreeLevelsArrayList.size() ;i++)
			System.out.println(degreeLevelsArrayList.get(i).getDegreeLevel()+"	"+degreeLevelsArrayList.get(i).degreeLevel + " " +(double)degreeLevelsArrayList.get(i).sumOfDegreesInTheNeighbourhoodOfThisDegreeLevel+ " " +degreeLevelsArrayList.get(i).totalNumberOfNodesInTheNeghbourhoodOfThisDegreeLevel+ " " +(double)degreeLevelsArrayList.get(i).sumOfDegreesInTheNeighbourhoodOfThisDegreeLevel/degreeLevelsArrayList.get(i).totalNumberOfNodesInTheNeghbourhoodOfThisDegreeLevel);		
	}
	
	void printDegreeDegreeCorrelationInMatlabFormat()
	{
		System.out.print("YDDC=[");
		for(int i=0; i< degreeLevelsArrayList.size() ;i++)
		{
			//if(degreeLevelsArrayList.get(i).getDegreeLevel()!=0)
			{
				if(degreeLevelsArrayList.get(i).sumOfDegreesInTheNeighbourhoodOfThisDegreeLevel==0)
					System.out.print(0.0);					
				else					
					System.out.print((double)degreeLevelsArrayList.get(i).sumOfDegreesInTheNeighbourhoodOfThisDegreeLevel/degreeLevelsArrayList.get(i).totalNumberOfNodesInTheNeghbourhoodOfThisDegreeLevel);
				if(i!=degreeLevelsArrayList.size()-1)
					System.out.print(",");
			}
		}
		System.out.println("]");			
		
		
	}
	
	void printMinimalList()
	{
		System.out.println("Now printing the degree statistics array:");
		for(int i=0; i< degreeLevelsArrayList.size() ;i++)
			System.out.println(degreeLevelsArrayList.get(i).getDegreeLevel()+"	"+degreeLevelsArrayList.get(i).getDegreeOfTheLevel() + " " +(double)degreeLevelsArrayList.get(i).getDegreeOfTheLevel()/totalDegree); 
	}
	
	void printDegreeLevelsInMatlabFormat()
	{
		System.out.print("Xd=[");
		for(int i=0; i< degreeLevelsArrayList.size() ;i++)
		{
			//if(degreeLevelsArrayList.get(i).getDegreeLevel()!=0)
			{
				System.out.print(degreeLevelsArrayList.get(i).getDegreeLevel());
				if(i!=degreeLevelsArrayList.size()-1)
					System.out.print(",");
			}
		}
		System.out.println("]");		
	}
	
	void printDegreeOfTheLevelsProbabilityInMatlabFormat()
	{
		System.out.print("YdP=[");
		for(int i=0; i< degreeLevelsArrayList.size() ;i++)
		{
			//if(degreeLevelsArrayList.get(i).getDegreeLevel()!=0)
			{
				System.out.print((double)degreeLevelsArrayList.get(i).getDegreeOfTheLevel()/totalDegree);
				if(i!=degreeLevelsArrayList.size()-1)
					System.out.print(",");
			}
		}
		System.out.println("]");		
	}
	
	void printRichClubCoefficientsInMatlabFormat()
	{
		System.out.print("YrCC=[");
		for(int i=0; i< degreeLevelsArrayList.size() ;i++)
		{
			//if(degreeLevelsArrayList.get(i).getDegreeLevel()!=0)
			{
				System.out.print((double)degreeLevelsArrayList.get(i).richClubCoefficientOfDegreeLevel);
				if(i!=degreeLevelsArrayList.size()-1)
					System.out.print(",");
			}
		}
		System.out.println("]");		
	}
	
	void printWithZeros()
	{
		int currentIndexOfDegreeLevel=0;
		System.out.println("\nNow printing the degree statistics array with zeros:");
		for(int i=0; i<= degreeLevelsArrayList.get(degreeLevelsArrayList.size()-1).getDegreeLevel() ;i++)
		{
			if(degreeLevelsArrayList.get(currentIndexOfDegreeLevel).getDegreeLevel()==i)
			{
				//System.out.println(degreeLevelsArrayList.get(currentIndexOfDegreeLevel).getDegreeLevel()+"	"+(double)degreeLevelsArrayList.get(currentIndexOfDegreeLevel).getDegreeOfTheLevel()/totalDegree);
				System.out.print((double)degreeLevelsArrayList.get(currentIndexOfDegreeLevel).getDegreeOfTheLevel()/totalDegree+",");
				currentIndexOfDegreeLevel++;
			}
			else
			{
				//System.out.println(i+"	"+0);
				//System.out.print(0+",");
			}
		}		
	}
	
	public void sort()
	{
		Collections.sort(degreeLevelsArrayList);		
	}	
	
	public int getSizeOfDegreeLevels()
	{		
		return degreeLevelsArrayList.size();
	}
	
	public int getDegreeLevelAtIndex(Integer index)
	{		
		return degreeLevelsArrayList.get(index).degreeLevel;
	}	
	
	void setRichClubCoefficientOfDegreeLevel(int degreeLevel, Double richClubCoefficent)
	{
		degreeLevelsArrayList.get(degreeLevel).richClubCoefficientOfDegreeLevel = richClubCoefficent;		
	}
}
